const Moment = require('moment');
const ecc = require('eosjs-ecc');
const { estimateFee, isAccountExist } = require('./lib/eos');
const { quickPay } = require('./lib/quickPay');
const { wapPay } = require('./lib/wapPay');
const { eosPay } = require('./lib/eosPay');
const { wxPay } = require('./lib/wxPay');
const logger = require('./utils/logger');
const models = require('./models');
const config = require('./config/config.json');

const serviceUrl = config[process.env.NODE_ENV].serviceUrl;
const defaultReceiver = process.env.EOS_ACCOUNT_NAME;  // 默认收款账号

const quickPayNotifyUrl = `${serviceUrl}/quickpaycb`;
const wapPayNotifyUrl = `${serviceUrl}/wappaycb`;
const wxPayNotifyUrl = `${serviceUrl}/wxpaycb`

module.exports = function (server) {
    const io = require('socket.io')(server);
    const billno2socket = {};

    // 支付结果返回
    const sendPaidResult = async (spbillno, result) => {
        let socket = billno2socket[spbillno];
        if (socket) {
            // 需要后端返回用户名称以做提示
            // 兼容其他支付模块, 只需在此处添加查询用户名称的方法即可, 若已经返回有用户名称, 则不进行数据库查询操作
            if (!result.accountName) {
                const acReq = await models.AccountCreationRequest.findOne({ where: { billNo: spbillno } });
                result.accountName = acReq ? acReq.accountName : '';
            }
            // 再次判断是否有该订单对应的socket, 有的话才触发消息发送, 避免发送多个消息
            // 此处做该处理主要因为第三方支付交互信息可能会触发多个socket发送消息事件, 会导致前端接收重复的消息提示, 此处就是为了避免多个消息发送事件
            if (billno2socket[spbillno]) {
                socket.emit('paidResult', result);
                // 返回消息后清空socket, 避免多次触发
                delete billno2socket[spbillno]
            }
        }
    }

    io.of('/account').on('connection', (socket) => {
        logger.info('Socket.io connection made:', socket.id);

        socket.on('disconnect', () => {
            logger.info('Socket.io disconnected:', socket.io);
            //TODO: remove socket from billno2socket
        });

        socket.on('createAccount', async (accountName, publicKey, payType, cb) => {
            logger.info(`accountName: ${accountName}, publicKey: ${publicKey}, payType: ${payType}`);
            let errback = data => {
                logger.error(data);
                cb(data);
            }

            if (!accountName) {
                return errback({ code: 400, message: 'Parameter account name is missing.' });
            }

            if (/^[1-5a-z]{12}$/.test(accountName) == false) {
                return errback({ code: 400, message: 'Parameter account name is invalid.' });
            }

            if (!publicKey) {
                return errback({ code: 400, message: 'Parameter public key is missing.' });
            }

            if (!payType) {
                return errback({ code: 400, message: 'Parameter pay type is missing.' });
            }

            if (!ecc.isValidPublic(publicKey)) {
                return errback({ code: 400, message: 'Parameter invalid: public key' });
            }

            try {
                logger.info('Checking account existence...');
                if (await isAccountExist(accountName)) {
                    return errback({ code: 409, message: `Account name '${accountName}' has already been used!` });
                }

                // Save request and get its unique ID: spbillno
                let billNo = Moment().format('YYYYMMDD') + Math.round(Math.random() * 100000).toString().padStart(5, '0');

                // 检测账号是否已存在, 并获取最新的价格数据fee
                /**
                 * fee: 最新的eos价格相关的数据
                 * acReq: 用户在数据库中的信息
                 * code: 错误状态码
                 * message: 错误消息
                */
                const { fee, payment, acReq, code, message } = await checkAndCreate(accountName, publicKey, billNo)
                if (code) {
                    return errback({ code: code, message: message });
                }
                logger.info(acReq.get({ plain: true }));

                // Billing
                let resp;
                if (payType == 'quickpay') {
                    resp = await quickPay(billNo, fee, quickPayNotifyUrl);
                } else if (payType == 'alipay') {
                    resp = await wapPay(billNo, fee, wapPayNotifyUrl);
                } else if (payType == 'nbhpay') {
                    resp = { retcode: 0, openUrl: `${serviceUrl}/pay/nbhpay?spbillno=${encodeURIComponent(billNo)}` };
                } else if (payType == 'wxpay') {
                    resp = await wxPay(billNo, fee, wxPayNotifyUrl);
                } else if (payType == 'eospay') {
                    resp = await eosPay(fee, acReq); // eos支付
                } else {
                    return errback({ code: 400, message: `Unsupport pay type: '${payType}'!` });
                }

                if (resp.retcode != 0) {
                    return errback({ code: 500, errcode: resp.retcode, message: resp.retmsg, spbillno: billNo });
                }

                billno2socket[billNo] = socket;

                // Send payment URL to client
                return cb({
                    code: 0,
                    message: 'success',
                    openUrl: resp.openUrl || '',
                    spbillno: billNo,
                    memo: resp.memo || '', // 新增备注标识码
                    transferAccount: defaultReceiver, // 收款默认账号
                    payment: payment ? ((payment > 1) ? parseFloat(payment.toFixed(1)) : 1) : 0 // 新增此次支付需要消耗多少费用(小于1取整为1, 大于1就取小数点后一位四舍五入) 主要是为了防止数据波动太大导致创建账号不成功
                });
            }
            catch (e) {
                logger.error("createAccount-error", e)
                return errback({ code: 500, message: e.message });
            }
        });
    });

    return {
        io,
        sendPaidResult
    };
};

// 创建账号并查询当前创建账号所需的费用
const checkAndCreate = async (accountName, publicKey, billNo) => {
    // 查询账号是否已存在
    logger.info('Checking if account info was already written to database.');
    let acReq = await fetchAccount(accountName, publicKey)
    // 账号存在, 判断是否为未成功创建的账号、判断账号的publicKey是否相同、(若账号创建失败, 或者账号的publicKey不一致可重新创建)
    if (acReq && acReq.status != 'failToCreate') {
        logger.info('Found account info in database.');
        // 账号已存在, 更新订单号
        if (acReq.status == 'initial') {
            acReq.billNo = billNo
            await acReq.save()
            const { totalCostCNY, totalCost } = await estimateFee()
            return { "fee": totalCostCNY, "payment": totalCost, "acReq": acReq }
        } else {
            // 账号状态不为initial,则证明已经有该账号
            return { code: 401, message: `The account is exists` }
        }
    } else {
        logger.info('Account info is not in database.');
        // 异步并发新建数据和查询实时价格数据、最终等待2个结果返回
        const [newAcReq, { totalCostCNY, totalCost}] = await Promise.all([
            createAccount(accountName, publicKey, billNo)
            ,
            estimateFee() // Estimate fee of creating account
        ])
        return { "fee": totalCostCNY, "payment": totalCost, "acReq": newAcReq }
    }
}

// 检测账号是否已存在(只查询24小时以内的)
const fetchAccount = async (accountName, publicKey) => {
    const ret = await models.AccountCreationRequest.findOne({
        where: {
            accountName: accountName,
            publicKey: publicKey,
            createdAt: {
                $gte: Moment().add(-1, "days")
            }
        }
    })
    return ret;
}

// 创建新账号入库记录
const createAccount = async (accountName, publicKey, billNo) => {
    logger.info('Writing account info to database.');
    const ret = await models.AccountCreationRequest.create({
        accountName: accountName,
        publicKey: publicKey,
        billNo: billNo,
        status: 'initial'
    })
    logger.info('Account info was written to database.');
    return ret;
}